using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using JigLibX;
using JigLibX.Collision;
using JigLibX.Geometry;
using JigLibX.Physics;

namespace mirror
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        public SpriteBatch spriteBatch;

        Texture2D bulletSprite;
        Model characterModel;
        Model levelModel;
        
        Song song;

        int score = 0;
        const int TIME_LIMIT_SECONDS = 60 * 3000;
        int timeRoundEndTime = TIME_LIMIT_SECONDS;
        bool timeUp = false;

        bool jumping = false;

        static Actor charActor;
        const int NUM_ENEMIES = 20;
        float cameraHeight = 15.0f;
        float cameraHeightIncrement = 0.2f;
        Vector2 PLAYER_START = new Vector2( -38.0f, 30.0f);
        const float PLAYER_START_HEADING = MathHelper.PiOver2;
        EnemyActor[] enemyActors;
        const float BULLET_SPEED = 2.0f;
        cRigidRenderable rigidLvl;
        List<LaserShot> mLaserShots;
        const int filter_green = 0;
        const int filter_red = 1;
        int filterState = filter_green;

        cVFXExplosion3D xplosion;

        PhysicsSystem physicsSystem;
        PhysicObjects.TriangleMeshObject levelCollisionMesh;
        Body characterBody;
        Sphere characterVolume;

        Vector3 cameraPosition;

        Model enemyModel,enemyModel2;

        const int gunCooldown_mseconds = 200;
        const int filterCooldown_mseconds = 1;
        int filterCooldown;
        int gunCooldown = 5000;

        Texture2D loadingScreen;

        float loadingScreenDuration = 2000.0f;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = 1920;
            graphics.PreferredBackBufferHeight = 1080;
            //graphics.IsFullScreen = true;
            Content.RootDirectory = "Content";

            physicsSystem = new PhysicsSystem();

            graphics.SynchronizeWithVerticalRetrace = false;
            this.IsFixedTimeStep = false;
            physicsSystem.CollisionSystem = new CollisionSystemSAP();

            physicsSystem.EnableFreezing = true;
            physicsSystem.SolverType = PhysicsSystem.Solver.Normal;
            physicsSystem.CollisionSystem.UseSweepTests = true;

            physicsSystem.NumCollisionIterations = 8;
            physicsSystem.NumContactIterations = 8;
            physicsSystem.NumPenetrationRelaxtionTimesteps = 15;

            filterCooldown = 0;
            //shootCooldown = 0;
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();

            new Camera();
            Camera.Instance.Create(new Vector2(graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight), new Vector2(1, 1));
            //Camera.Instance.SetTarget(new Vector3(0, 3, 5));
            Camera.Instance.SetTarget(new Vector3(charActor.pos.X, 3, charActor.pos.Y));


            mLaserShots = new List<LaserShot>();

        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            cFontRenderer.Instance.Initialise(GraphicsDevice);
            cFontRenderer.Instance.AddFont(eFonts.Standard, (SpriteFont)Content.Load<SpriteFont>("Arial"));

            new EffectManager();
            EffectManager.Instance.SetContent(Content);

            new SpriteManager();
            SpriteManager.Instance.Initialise(Content, spriteBatch);

            new cRenderManager();
            cRenderManager.Instance.Initialize(Content, GraphicsDevice);
            new cLocalFxManager();
            cLocalFxManager.Instance.Init(Content, GraphicsDevice);

            new ParticleManager3D();

            new cSoundManager();
            cSoundManager.Instance.Init(Content);

            // TODO: use this.Content to load your game content here
            //bulletSprite = Content.Load<Texture2D>("ohai");//bulletSprite");
            characterModel = Content.Load<Model>("finalModelSadface_white");
            enemyModel = Content.Load<Model>("finalModelSadface_red");
            enemyModel2 = Content.Load<Model>("finalModelSadface_blue");
            levelModel = Content.Load<Model>("levelFile_proto");
            loadingScreen = Content.Load<Texture2D>("LoadingScreen");

            levelCollisionMesh = new PhysicObjects.TriangleMeshObject(this, levelModel, Matrix.Identity, Vector3.Zero);
            levelCollisionMesh.PhysicsBody.Immovable = true;
            physicsSystem.AddBody(levelCollisionMesh.PhysicsBody);
            levelCollisionMesh.PhysicsBody.EnableBody();

            new cRenderManager();
            cRenderManager.Instance.Initialize(Content, GraphicsDevice);
            new cLocalFxManager();
            cLocalFxManager.Instance.Init(Content, GraphicsDevice);

            GraphicsDevice.RasterizerState = new RasterizerState();

            // BabisTEmp:

            charActor = new Actor(characterModel, PLAYER_START);
            charActor.heading = PLAYER_START_HEADING;

            physicsSystem.AddBody(charActor.body);
            charActor.body.EnableBody();

            //charActor.mTransform.M11 *= 0.01f; charActor.mTransform.M22 *= 0.01f; charActor.mTransform.M33 *= 0.01f;
            //charActor.mTransform.M41 = 2; charActor.mTransform.M42 = 3; charActor.mTransform.M43 = 5.0f;
            rigidLvl = new cRigidRenderable(levelModel);
            cRenderManager.Instance.AddRigid(rigidLvl);
            cRenderManager.Instance.AddRigid(charActor);

            // Create enemies
            Random random = new Random();
            enemyActors = new EnemyActor[NUM_ENEMIES];
            Random r = new Random();
            for (int i = 0; i < NUM_ENEMIES; ++i )
            {
                Vector2 rndVec = new Vector2( (float)r.NextDouble(), (float)r.NextDouble() );
                rndVec -= new Vector2(0.5f);
                rndVec *= 50.0f;

                enemyActors[i] = new EnemyActor(((i%2) == 0) ? enemyModel : enemyModel2, rndVec, charActor);

                cRenderManager.Instance.AddRigid(enemyActors[i]);

                physicsSystem.AddBody(enemyActors[i].body);
                enemyActors[i].body.EnableBody();
            }

            // Play music
            song = Content.Load<Song>("technio");  // Put the name of your song in instead of "song_title"
            MediaPlayer.IsRepeating = true;
            MediaPlayer.Play(song);
            MediaPlayer.IsRepeating = true;
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (loadingScreenDuration > 0) return;
            float deltaT = gameTime.ElapsedGameTime.Milliseconds / 1000.0f;
            filterCooldown += gameTime.ElapsedGameTime.Milliseconds;

            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape) )
                this.Exit();

            if (gameTime.TotalGameTime.TotalSeconds > timeRoundEndTime)
            {
                timeUp = true;
                if (GamePad.GetState(PlayerIndex.One).IsButtonDown(Buttons.Start))
                {
                    //reset game
                    timeRoundEndTime = (int)gameTime.TotalGameTime.TotalSeconds + TIME_LIMIT_SECONDS;
                    timeUp = false;
                    //todo reset stuff here
                }
                return;
            }

            levelCollisionMesh.PhysicsBody.SetActive();

            InputHandler(gameTime);
            UpdateLaserShots(deltaT);
            //Console.Out.WriteLine("x: " + charActor.pos.X + " y: " + charActor.pos.Y);

            foreach ( EnemyActor enemy in enemyActors )
            {
                enemy.Update();
            }

            cRenderManager.Instance.Update(gameTime);
            cVFXManager.Instance.Update(gameTime);
            Camera.Instance.Update();

            //--filterCooldown;
            //--shootCooldown;

            int filterCooldownMax = 100;
            int shootCooldownMax = 10;

            if (( Keyboard.GetState().IsKeyDown(Keys.Z) || (GamePad.GetState(PlayerIndex.One).Buttons.A == ButtonState.Pressed) )
                && filterCooldown >= filterCooldown_mseconds && filterState != filter_green )
            {
                filterState = filter_green;
                //filterCooldown = filterCooldownMax;  
                filterCooldown = 0;  

                //cRenderManager.Instance.StartThud(new Vector3(0, 4, 0), 1, 0.15f, 2);
                Camera.Instance.Shake(0.25f, 8);
                cSoundManager.Instance.PlaySound("onLaserFire");
                //cRenderManager.Instance.ActivateContrastPostProc(4.0f);
                cRenderManager.Instance.ActivateColorFilterPostProc(0.0f);
            }
            if (( Keyboard.GetState().IsKeyDown(Keys.X) || (GamePad.GetState(PlayerIndex.One).Buttons.B == ButtonState.Pressed) )
                && filterCooldown >= filterCooldown_mseconds && filterState != filter_red )
            {
                filterState = filter_red;
                //filterCooldown = filterCooldownMax;  
                filterCooldown = 0;  
                Camera.Instance.Shake(0.25f, 8);
                cSoundManager.Instance.PlaySound("onLaserFire");
                cRenderManager.Instance.ActivateColorFilterPostProc(1.0f);
            }

            PhysicsUpdate();

            base.Update(gameTime);
        }

        protected void PhysicsUpdate()
        {
            physicsSystem.Integrate(1.0f / 60.0f);
        }

        public void UpdateLaserShots(float zDt)
        {
            for (int i = 0; i < mLaserShots.Count; i++)
            {
                if (!mLaserShots[i].Update(zDt))
                    mLaserShots.RemoveAt(i);
            }
        }

        class ImmovableSkinPredicate : CollisionSkinPredicate1
        {
            public override bool ConsiderSkin(CollisionSkin skin0)
            {
                if (skin0.Owner != null && !skin0.Owner.Immovable && skin0.Owner != charActor.body)
                    return true;

                else
                    return false;
            }
        }

        bool shootBullet = false;

        void InputHandler(GameTime gameTime)
        {
            PlayerIndex playerIdx = PlayerIndex.One;
            GamePadState controllerState = GamePad.GetState(playerIdx);
            KeyboardState keyboardState = Keyboard.GetState();
            //if (controllerState.IsConnected)
            {
                if ( (controllerState.Buttons.A == ButtonState.Pressed )
                 || (controllerState.Buttons.B == ButtonState.Pressed ) )
                {
                    GamePad.SetVibration(playerIdx, 0.5f, 0.5f);
                    //shootBullet = true;
                }
                else
                {
                    GamePad.SetVibration(playerIdx, 0.0f, 0.0f);
                    //shootBullet = false;
                }

                Vector2 moveVec = new Vector2( controllerState.ThumbSticks.Left.Y,
                                               controllerState.ThumbSticks.Left.X
                    );
                Vector2 lookVec = controllerState.ThumbSticks.Right;

                // Ignore controller if keys pressed.
                if (keyboardState.IsKeyDown(Keys.W) ||
                    keyboardState.IsKeyDown(Keys.A) ||
                    keyboardState.IsKeyDown(Keys.S) ||
                    keyboardState.IsKeyDown(Keys.D)
                    )
                {
                    moveVec = new Vector2(0, 0);
                }
                if (keyboardState.IsKeyDown(Keys.W))
                {
                    moveVec += new Vector2(1,0);
                }
                if (keyboardState.IsKeyDown(Keys.A))
                {
                    moveVec += new Vector2(0,-1);
                }
                if (keyboardState.IsKeyDown(Keys.S))
                {
                    moveVec += new Vector2(-1,0);
                }
                if (keyboardState.IsKeyDown(Keys.D))
                {
                    moveVec += new Vector2(0,1);
                }

                moveVec *= 0.4f * gameTime.ElapsedGameTime.Milliseconds;

                // Ignore controller if keys pressed.
                if (keyboardState.IsKeyDown(Keys.Up) ||
                    keyboardState.IsKeyDown(Keys.Left) ||
                    keyboardState.IsKeyDown(Keys.Down) ||
                    keyboardState.IsKeyDown(Keys.Right)
                    )
                {
                    lookVec = new Vector2(0, 0);
                }
                if (keyboardState.IsKeyDown(Keys.Right))
                {
                    lookVec += new Vector2(1,0);
                }
                if (keyboardState.IsKeyDown(Keys.Down))
                {
                    lookVec += new Vector2(0,-1);
                }
                if (keyboardState.IsKeyDown(Keys.Left))
                {
                    lookVec += new Vector2(-1,0);
                }
                if (keyboardState.IsKeyDown(Keys.Up))
                {
                    lookVec += new Vector2(0,1);
                }

                //charActor.heading = (float)(Math.Atan2(charActor.vel.X, charActor.vel.Y));//+ MathHelper.ToRadians(90.0f) );
                charActor.heading = -(float)(Math.Atan2(lookVec.X, lookVec.Y)) - (float)(Math.PI)*0.5f;
                Console.Out.WriteLine("charActor.heading: " + charActor.heading);
                charActor.Update(); // update position etc.
                float lookLen = lookVec.Length();
                gunCooldown += gameTime.ElapsedGameTime.Milliseconds;
                if (lookLen > 0.05f && gunCooldown > gunCooldown_mseconds)
                {
                    gunCooldown = 0;
                    lookVec.Normalize();
                    //todo
                    //projectiles.Add( new Projectile( charActor.pos, lookVec * BULLET_SPEED ) );
                    Matrix m = Matrix.CreateRotationY(charActor.heading) * Matrix.CreateTranslation(charActor.body.Position);
                    mLaserShots.Add(new LaserShot(0.25f, 80, 0.85f, 1, Color.Red, Color.White, m, 0.3f, 0.7f, 0.1f));

                    float frac;
                    CollisionSkin cs;
                    Vector3 posOut;
                    Vector3 normalOut;
                    float distance = 100;
                    physicsSystem.CollisionSystem.SegmentIntersect(out frac, out cs, out posOut, out normalOut, new Segment(m.Translation, m.Forward * distance), new ImmovableSkinPredicate());

                    foreach (EnemyActor e in enemyActors)
                    {
                        if (cs != null && cs.Owner == e.body )
                        {
                            e.OnHit( 1.0f );
                        }
                    }

                }

                //Console.Out.WriteLine("x: " + moveVec.X + " y: " + moveVec.Y);
                //Console.Out.WriteLine("charActor.heading: " + charActor.heading);

                if (moveVec.Length() > 0.05f)
                {
                    //charActor.acc.Y = -charActor.acc.Y;
                    charActor.acc.X += moveVec.X * 0.6f;
                    charActor.acc.Y += moveVec.Y * 0.6f;
                }

                //Debug reset state
                if (controllerState.Buttons.Start == ButtonState.Pressed ||
                    keyboardState.IsKeyDown(Keys.Enter))
                {
                    charActor.pos = PLAYER_START;
                    charActor.vel = new Vector2(0.0f);
                    charActor.acc = new Vector2(0.0f);
                    charActor.heading = PLAYER_START_HEADING;
                }
            }
            if ( Keyboard.GetState().IsKeyDown(Keys.O) )
            {
                cameraHeight += cameraHeightIncrement;
            }
            if ( Keyboard.GetState().IsKeyDown(Keys.L) )
            {
                cameraHeight -= cameraHeightIncrement;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Space) && !jumping)
            {
                charActor.body.ApplyWorldImpulse(new Vector3(0.0f, 10.0f, 0.0f));
                jumping = true;
            }
            if(!Keyboard.GetState().IsKeyDown(Keys.Space) && jumping)
            {
                jumping = false;
            }

        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            
            GraphicsDevice device = graphics.GraphicsDevice;
            GraphicsDevice.Clear(Color.CornflowerBlue);

            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;

            // Set the positions of the camera in world space, for our view matrix.
            //cameraPosition = new Vector3(0.0f, 15.0f, 20.0f);
            cameraPosition = new Vector3(charActor.pos.X - 5.0f, cameraHeight, charActor.pos.Y);
            Camera.Instance.Position = cameraPosition;
            Camera.Instance.SetTarget(new Vector3(charActor.pos.X, 3, charActor.pos.Y));

            cRenderManager.Instance.Render();
            cFontRenderer.Instance.Draw();
            cVFXManager.Instance.Draw();
            cRenderManager.Instance.RenderPostProc();

            // Add UI drawing after begin
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, DepthStencilState.Default, null);
            //spriteBatch.Draw(bulletSprite, new Vector2(5, 5), Color.White);
            spriteBatch.End();


            if (loadingScreenDuration > 0)
            {
                spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, SamplerState.PointClamp, DepthStencilState.Default, null);
                spriteBatch.Draw(loadingScreen, new Microsoft.Xna.Framework.Rectangle(0, 0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight), Color.White);
                spriteBatch.End();
                loadingScreenDuration -= gameTime.ElapsedGameTime.Milliseconds;

                return;
            }

            base.Draw(gameTime);
        }
    }
}
